Программирование сетевых приложений

Потоки ввода/вывода и работа с файлами в C++ и Qt

Программирование сетевых приложений

Содержание лекции

  • Введение в потоки ввода/вывода
  • Консольный ввод и вывод
  • Работа с файлами в C++
  • Классы и интерфейсы потоков
  • Байтовые и символьные потоки
  • Буферизированные потоки
  • Потоки в Qt
  • Сериализация данных
  • Преимущества потоковой обработки
  • Практические примеры
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Введение в потоки ввода/вывода

Потоки ввода/вывода (I/O streams) являются фундаментальным механизмом для обработки данных в C++. Они предоставляют единообразный интерфейс для работы с различными источниками и приемниками данных: файлами, консолью, сетевыми соединениями и другими устройствами.

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Основные преимущества потоков

  1. Унификация - единый интерфейс для различных типов данных
  2. Безопасность - автоматическая обработка ошибок и исключений
  3. Гибкость - легкость в расширении и настройке
  4. Эффективность - оптимизированная буферизация и обработка
  5. Типобезопасность - компилятор контролирует типы данных
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Иерархия потоков в C++

ios_base
├── ios
│   ├── istream
│   │   ├── ifstream (файлы)
│   │   ├── istringstream (строки)
│   │   └── iostream
│   ├── ostream
│   │   ├── ofstream (файлы)
│   │   ├── ostringstream (строки)
│   │   └── iostream
│   └── iostream
│       ├── fstream
│       └── stringstream
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Консольный ввод и вывод

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Базовые операции ввода/вывода

#include <iostream>
#include <string>

int main() {
    // Вывод данных
    std::cout << "Hello, World!" << std::endl;
    std::cout << "Введите ваше имя: ";
    
    // Ввод строки
    std::string name;
    std::getline(std::cin, name);
    
    // Форматированный вывод
    int age = 25;
    std::cout << "Привет, " << name << "! Вам " << age << " лет." << std::endl;
    
    return 0;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Форматирование вывода

#include <iostream>
#include <iomanip>

void demonstrateFormatting() {
    double pi = 3.14159265359;
    int number = 42;
    
    // Управление точностью
    std::cout << std::fixed << std::setprecision(2) << pi << std::endl;
    
    // Управление шириной поля
    std::cout << std::setw(10) << number << std::endl;
    std::cout << std::left << std::setw(10) << "Hello" << "World" << std::endl;
    
    // Шестнадцатеричный и восьмеричный формат
    std::cout << std::hex << number << std::endl;
    std::cout << std::oct << number << std::endl;
    std::cout << std::dec << number << std::endl;
    
    // Булевы значения
    bool flag = true;
    std::cout << std::boolalpha << flag << std::endl;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Чтение различных типов данных

#include <iostream>
#include <string>
#include <vector>

class ConsoleReader {
public:
    static int readInt(const std::string& prompt) {
        int value;
        std::cout << prompt;
        while (!(std::cin >> value)) {
            std::cin.clear(); // Сброс состояния потока
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "Неверный ввод. Попробуйте снова: ";
        }
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        return value;
    }
    
    static double readDouble(const std::string& prompt) {
        double value;
        std::cout << prompt;
        while (!(std::cin >> value)) {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "Неверный ввод. Попробуйте снова: ";
        }
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        return value;
    }
    
    static std::string readString(const std::string& prompt) {
        std::cout << prompt;
        std::string value;
        std::getline(std::cin, value);
        return value;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Работа с файлами в C++

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Базовые операции с файлами

#include <fstream>
#include <iostream>
#include <string>

class FileManager {
public:
    static bool writeToFile(const std::string& filename, const std::string& content) {
        std::ofstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Не удалось открыть файл для записи: " << filename << std::endl;
            return false;
        }
        
        file << content;
        return file.good();
    }
    
    static std::string readFromFile(const std::string& filename) {
        std::ifstream file(filename);
        if (!file.is_open()) {
            std::cerr << "Не удалось открыть файл для чтения: " << filename << std::endl;
            return "";
        }
        
        std::string content((std::istreambuf_iterator<char>(file)),
                           std::istreambuf_iterator<char>());
        return content;
    }
    
    static bool appendToFile(const std::string& filename, const std::string& content) {
        std::ofstream file(filename, std::ios::app);
        if (!file.is_open()) {
            std::cerr << "Не удалось открыть файл для добавления: " << filename << std::endl;
            return false;
        }
        
        file << content;
        return file.good();
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Режимы открытия файлов

#include <fstream>

void demonstrateFileModes() {
    // Запись с перезаписью файла
    std::ofstream out1("file1.txt", std::ios::out);
    
    // Добавление в конец файла
    std::ofstream out2("file2.txt", std::ios::app);
    
    // Бинарный режим записи
    std::ofstream out3("file3.bin", std::ios::binary);
    
    // Чтение и запись
    std::fstream io("file4.txt", std::ios::in | std::ios::out);
    
    // Создание нового файла (ошибка, если существует)
    std::ofstream out4("file5.txt", std::ios::out | std::ios::trunc);
    
    // Позиционирование в конец файла
    std::fstream io2("file6.txt", std::ios::in | std::ios::out | std::ios::ate);
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Позиционирование в файле

#include <fstream>
#include <iostream>

class FilePositionManager {
public:
    static void demonstratePositioning() {
        std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::trunc);
        
        if (!file.is_open()) {
            std::cerr << "Не удалось открыть файл" << std::endl;
            return;
        }
        
        // Запись данных
        file << "Hello, World! This is a test file.";
        
        // Перемещение на начало файла
        file.seekg(0, std::ios::beg);
        
        // Чтение первых 5 символов
        char buffer[6] = {0};
        file.read(buffer, 5);
        std::cout << "Первые 5 символов: " << buffer << std::endl;
        
        // Перемещение на 7 позицию от начала
        file.seekg(7, std::ios::beg);
        
        // Чтение следующих 5 символов
        file.read(buffer, 5);
        std::cout << "Символы с 7 по 11: " << buffer << std::endl;
        
        // Определение текущей позиции
        std::streampos pos = file.tellg();
        std::cout << "Текущая позиция: " << pos << std::endl;
        
        // Перемещение на 10 символов от конца
        file.seekg(-10, std::ios::end);
        file.read(buffer, 5);
        std::cout << "Символы за 10 позиций от конца: " << buffer << std::endl;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Классы и интерфейсы потоков

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Базовые классы потоков

#include <iostream>
#include <sstream>
#include <fstream>

class StreamHierarchyDemo {
public:
    // Работа с базовым классом std::istream
    static void readFromStream(std::istream& stream) {
        std::string line;
        while (std::getline(stream, line)) {
            std::cout << "Прочитано: " << line << std::endl;
        }
    }
    
    // Работа с базовым классом std::ostream
    static void writeToStream(std::ostream& stream, const std::string& data) {
        stream << data << std::endl;
    }
    
    static void demonstrateHierarchy() {
        // Работа с консолью
        std::cout << "=== Работа с консолью ===" << std::endl;
        writeToStream(std::cout, "Hello from console!");
        
        // Работа со строкой
        std::cout << "=== Работа со строкой ===" << std::endl;
        std::stringstream ss;
        writeToStream(ss, "Hello from string stream!");
        std::cout << "Содержимое строкового потока: " << ss.str() << std::endl;
        
        // Работа с файлом
        std::cout << "=== Работа с файлом ===" << std::endl;
        std::ofstream file("test.txt");
        writeToStream(file, "Hello from file stream!");
        file.close();
        
        // Чтение из файла
        std::ifstream infile("test.txt");
        readFromStream(infile);
        infile.close();
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Пользовательские манипуляторы

#include <iostream>
#include <iomanip>

// Пользовательский манипулятор для вывода в шестнадцатеричном формате
struct hex_format {
    int value;
    hex_format(int v) : value(v) {}
};

std::ostream& operator<<(std::ostream& os, const hex_format& hf) {
    return os << "0x" << std::hex << std::uppercase << std::setw(4) 
              << std::setfill('0') << hf.value << std::dec;
}

// Манипулятор для вывода текущего времени
struct timestamp {
    std::chrono::system_clock::time_point time;
    timestamp(std::chrono::system_clock::time_point t = std::chrono::system_clock::now()) 
        : time(t) {}
};

std::ostream& operator<<(std::ostream& os, const timestamp& ts) {
    auto time_t = std::chrono::system_clock::to_time_t(ts.time);
    os << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
    return os;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Байтовые и символьные потоки

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Работа с бинарными данными

#include <fstream>
#include <vector>
#include <cstdint>

class BinaryStreamManager {
public:
    // Запись структуры в бинарный файл
    struct DataRecord {
        uint32_t id;
        double value;
        char name[32];
    };
    
    static bool writeBinaryData(const std::string& filename, 
                               const std::vector<DataRecord>& records) {
        std::ofstream file(filename, std::ios::binary);
        if (!file.is_open()) {
            return false;
        }
        
        // Запись количества записей
        size_t count = records.size();
        file.write(reinterpret_cast<const char*>(&count), sizeof(count));
        
        // Запись самих записей
        for (const auto& record : records) {
            file.write(reinterpret_cast<const char*>(&record), sizeof(record));
        }
        
        return file.good();
    }
    
    static std::vector<DataRecord> readBinaryData(const std::string& filename) {
        std::vector<DataRecord> records;
        std::ifstream file(filename, std::ios::binary);
        
        if (!file.is_open()) {
            return records;
        }
        
        // Чтение количества записей
        size_t count;
        file.read(reinterpret_cast<char*>(&count), sizeof(count));
        
        // Чтение записей
        records.reserve(count);
        for (size_t i = 0; i < count; ++i) {
            DataRecord record;
            file.read(reinterpret_cast<char*>(&record), sizeof(record));
            records.push_back(record);
        }
        
        return records;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Преобразование между текстовыми и бинарными данными

#include <sstream>
#include <iomanip>

class DataConverter {
public:
    // Преобразование бинарных данных в шестнадцатеричную строку
    static std::string toHex(const std::vector<uint8_t>& data) {
        std::ostringstream oss;
        oss << std::hex << std::setfill('0');
        
        for (uint8_t byte : data) {
            oss << std::setw(2) << static_cast<int>(byte) << " ";
        }
        
        return oss.str();
    }
    
    // Преобразование шестнадцатеричной строки в бинарные данные
    static std::vector<uint8_t> fromHex(const std::string& hexStr) {
        std::vector<uint8_t> result;
        std::istringstream iss(hexStr);
        
        std::string byteStr;
        while (iss >> byteStr) {
            if (byteStr.length() == 2) {
                uint8_t byte = static_cast<uint8_t>(std::stoi(byteStr, nullptr, 16));
                result.push_back(byte);
            }
        }
        
        return result;
    }
    
    // Безопасное чтение бинарных данных
    static bool readBinarySafe(std::istream& stream, void* buffer, size_t size) {
        stream.read(static_cast<char*>(buffer), size);
        return stream.good() && stream.gcount() == static_cast<std::streamsize>(size);
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Буферизированные потоки

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Настройка буферизации

#include <fstream>
#include <iostream>

class BufferManager {
public:
    static void demonstrateBuffering() {
        // Отключение буферизации
        std::ofstream unbuffered("unbuffered.txt");
        unbuffered.setf(std::ios::unitbuf); // Буферизация после каждой операции
        
        // Ручная буферизация
        std::ofstream manual("manual.txt");
        char buffer[1024];
        manual.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
        
        // Проверка состояния буфера
        std::streambuf* buf = manual.rdbuf();
        
        std::cout << "Размер буфера: " << buf->pubseekoff(0, std::ios::cur, std::ios::out) << std::endl;
        
        // Принудительная очистка буфера
        manual << "Данные для записи";
        manual.flush(); // Принудительная запись в файл
        
        // Синхронизация с фактическим файлом
        manual.sync(); // Синхронизация буфера с диском
    }
    
    // Пользовательский буфер для оптимизации
    class CustomBuffer {
    private:
        static constexpr size_t BUFFER_SIZE = 8192;
        char buffer[BUFFER_SIZE];
        std::ofstream& stream;
        
    public:
        explicit CustomBuffer(std::ofstream& s) : stream(s) {
            stream.rdbuf()->pubsetbuf(buffer, BUFFER_SIZE);
        }
        
        void flush() {
            stream.flush();
        }
    };
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Производительность буферизированного ввода/вывода

#include <fstream>
#include <chrono>
#include <vector>
#include <iostream>

class PerformanceTester {
public:
    static void compareIOPerformance() {
        const size_t DATA_SIZE = 1024 * 1024; // 1 МБ данных
        std::vector<char> data(DATA_SIZE, 'A');
        
        // Тест небуферизированной записи
        auto start = std::chrono::high_resolution_clock::now();
        testUnbufferedWrite(data, "unbuffered.dat");
        auto end = std::chrono::high_resolution_clock::now();
        auto unbufferedTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
        
        // Тест буферизированной записи
        start = std::chrono::high_resolution_clock::now();
        testBufferedWrite(data, "buffered.dat");
        end = std::chrono::high_resolution_clock::now();
        auto bufferedTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
        
        std::cout << "Не буферизированная запись: " << unbufferedTime.count() << " мс" << std::endl;
        std::cout << "Буферизированная запись: " << bufferedTime.count() << " мс" << std::endl;
        std::cout << "Ускорение: " << static_cast<double>(unbufferedTime.count()) / bufferedTime.count() << "x" << std::endl;
    }
    
private:
    static void testUnbufferedWrite(const std::vector<char>& data, const std::string& filename) {
        std::ofstream file(filename, std::ios::binary);
        file.setf(std::ios::unitbuf); // Отключение буферизации
        
        for (char byte : data) {
            file.write(&byte, 1);
        }
    }
    
    static void testBufferedWrite(const std::vector<char>& data, const std::string& filename) {
        std::ofstream file(filename, std::ios::binary);
        file.write(data.data(), data.size());
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Потоки в Qt

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

QFile и QTextStream

#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QCoreApplication>

class QtFileManager {
public:
    static bool writeTextFile(const QString& filename, const QString& content) {
        QFile file(filename);
        
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
            qDebug() << "Не удалось открыть файл для записи:" << file.errorString();
            return false;
        }
        
        QTextStream stream(&file);
        stream.setCodec("UTF-8"); // Установка кодировки
        stream << content;
        
        file.close();
        return true;
    }
    
    static QString readTextFile(const QString& filename) {
        QFile file(filename);
        
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qDebug() << "Не удалось открыть файл для чтения:" << file.errorString();
            return QString();
        }
        
        QTextStream stream(&file);
        stream.setCodec("UTF-8");
        QString content = stream.readAll();
        
        file.close();
        return content;
    }
    
    static bool appendToFile(const QString& filename, const QString& content) {
        QFile file(filename);
        
        if (!file.open(QIODevice::Append | QIODevice::Text)) {
            qDebug() << "Не удалось открыть файл для добавления:" << file.errorString();
            return false;
        }
        
        QTextStream stream(&file);
        stream.setCodec("UTF-8");
        stream << content;
        
        file.close();
        return true;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Работа с бинарными данными в Qt

#include <QFile>
#include <QDataStream>
#include <QVector>
#include <QDebug>

class QtBinaryManager {
public:
    struct NetworkPacket {
        quint32 id;
        quint32 timestamp;
        QString sender;
        QByteArray data;
        
        // Операторы для сериализации
        friend QDataStream& operator<<(QDataStream& stream, const NetworkPacket& packet) {
            stream << packet.id << packet.timestamp << packet.sender << packet.data;
            return stream;
        }
        
        friend QDataStream& operator>>(QDataStream& stream, NetworkPacket& packet) {
            stream >> packet.id >> packet.timestamp >> packet.sender >> packet.data;
            return stream;
        }
    };
    
    static bool writeBinaryData(const QString& filename, const QVector<NetworkPacket>& packets) {
        QFile file(filename);
        
        if (!file.open(QIODevice::WriteOnly)) {
            qDebug() << "Не удалось открыть файл для записи:" << file.errorString();
            return false;
        }
        
        QDataStream stream(&file);
        stream.setVersion(QDataStream::Qt_6_0); // Установка версии формата
        
        // Запись количества пакетов
        stream << static_cast<quint32>(packets.size());
        
        // Запись пакетов
        for (const auto& packet : packets) {
            stream << packet;
        }
        
        file.close();
        return true;
    }
    
    static QVector<NetworkPacket> readBinaryData(const QString& filename) {
        QVector<NetworkPacket> packets;
        QFile file(filename);
        
        if (!file.open(QIODevice::ReadOnly)) {
            qDebug() << "Не удалось открыть файл для чтения:" << file.errorString();
            return packets;
        }
        
        QDataStream stream(&file);
        stream.setVersion(QDataStream::Qt_6_0);
        
        // Чтение количества пакетов
        quint32 count;
        stream >> count;
        
        // Чтение пакетов
        packets.reserve(count);
        for (quint32 i = 0; i < count; ++i) {
            NetworkPacket packet;
            stream >> packet;
            packets.append(packet);
        }
        
        file.close();
        return packets;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Потоковая обработка больших файлов в Qt

#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QCoreApplication>

class LargeFileProcessor {
public:
    static bool processLargeTextFile(const QString& inputFilename, 
                                    const QString& outputFilename,
                                    std::function<QString(const QString&)> processor) {
        QFile inputFile(inputFilename);
        QFile outputFile(outputFilename);
        
        if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qDebug() << "Не удалось открыть входной файл:" << inputFile.errorString();
            return false;
        }
        
        if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
            qDebug() << "Не удалось открыть выходной файл:" << outputFile.errorString();
            inputFile.close();
            return false;
        }
        
        QTextStream inputStream(&inputFile);
        QTextStream outputStream(&outputFile);
        
        inputStream.setCodec("UTF-8");
        outputStream.setCodec("UTF-8");
        
        // Обработка файла построчно для экономии памяти
        qint64 lineCount = 0;
        while (!inputStream.atEnd()) {
            QString line = inputStream.readLine();
            QString processedLine = processor(line);
            outputStream << processedLine << "\n";
            
            lineCount++;
            if (lineCount % 10000 == 0) {
                qDebug() << "Обработано строк:" << lineCount;
                QCoreApplication::processEvents(); // Обработка событий для предотвращения зависания
            }
        }
        
        inputFile.close();
        outputFile.close();
        
        qDebug() << "Обработка завершена. Всего строк:" << lineCount;
        return true;
    }
    
    static qint64 countLines(const QString& filename) {
        QFile file(filename);
        
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            return -1;
        }
        
        QTextStream stream(&file);
        stream.setCodec("UTF-8");
        
        qint64 count = 0;
        while (!stream.atEnd()) {
            stream.readLine();
            count++;
        }
        
        file.close();
        return count;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Работа с каталогами

std::filesystem (C++17) — кроссплатформенная библиотека для работы с файловой системой:

Компонент Назначение
std::filesystem::path Представление пути к файлу/каталогу
std::filesystem::directory_entry Элемент каталога (файл или подкаталог)
std::filesystem::directory_iterator Итератор по содержимому каталога
std::filesystem::recursive_directory_iterator Рекурсивный обход вложенных каталогов
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;

int main() {
    fs::path dir = "network_app";

    if (fs::exists(dir)) {
        std::cout << "Каталог '" << dir << "' существует.\n";

        for (const auto& entry : fs::directory_iterator(dir)) {
            std::cout << "  " << entry.path().filename()
                      << (entry.is_directory() ? " [DIR]" : "")
                      << " — " << fs::file_size(entry) << " байт\n";
        }
    } else {
        fs::create_directories(dir / "config");
        std::cout << "Создан каталог: " << fs::absolute(dir) << "\n";
    }
    return 0;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Работа с каталогами в Qt — класс QDir

Основные методы QDir:

Метод Назначение
exists() Проверка существования каталога
mkdir() / mkpath() Создание каталога (одного / вложенного)
entryList() Список файлов и подкаталогов с фильтрацией
rename() Переименование или перемещение
remove() Удаление файла; removeRecursively() — каталога
#include <QDir>
#include <QDebug>
#include <QCoreApplication>

void listFilteredFiles(const QString& dirPath) {
    QDir dir(dirPath);

    QStringList filters;
    filters << "*.conf" << "*.cfg" << "*.ini";
    dir.setNameFilters(filters);

    QFileInfoList files = dir.entryInfoList(
        QDir::Files | QDir::Readable,
        QDir::Name
    );

    qDebug() << "Файлы конфигурации в" << dirPath << ":";
    for (const QFileInfo& fi : files) {
        qDebug() << "  " << fi.fileName() << fi.size() << "байт";
    }

    QDir().mkpath(dirPath + "/certificates");
    QDir().mkpath(dirPath + "/logs");
    qDebug() << "Структура каталогов создана.";
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Практический пример: конфигурация сетевого приложения

config/
├── server.conf
├── certificates/
│   ├── cert.pem
│   └── key.pem
└── logs/
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QDebug>

class ConfigManager {
    QString basePath;

public:
    explicit ConfigManager(const QString& base)
        : basePath(base) {}

    bool initStructure() {
        QDir dir;
        if (!dir.mkpath(basePath + "/certificates")) {
            qDebug() << "Ошибка создания certificates/";
            return false;
        }
        if (!dir.mkpath(basePath + "/logs")) {
            qDebug() << "Ошибка создания logs/";
            return false;
        }

        QFile confFile(basePath + "/server.conf");
        if (!confFile.exists()) {
            if (!confFile.open(QIODevice::WriteOnly | QIODevice::Text))
                return false;
            QTextStream out(&confFile);
            out << "host=0.0.0.0\n"
                << "port=8080\n"
                << "max_connections=100\n"
                << "cert=" << (basePath + "/certificates/cert.pem") << "\n"
                << "key="  << (basePath + "/certificates/key.pem") << "\n";
            confFile.close();
            qDebug() << "Создан server.conf по умолчанию.";
        }
        return true;
    }

    QMap<QString, QString> loadConfig() {
        QMap<QString, QString> settings;
        QFile file(basePath + "/server.conf");
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            return settings;

        QTextStream in(&file);
        while (!in.atEnd()) {
            QString line = in.readLine().trimmed();
            if (line.isEmpty() || line.startsWith('#'))
                continue;
            int sep = line.indexOf('=');
            if (sep > 0)
                settings[line.left(sep)] = line.mid(sep + 1);
        }
        file.close();
        return settings;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Сериализация

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Сериализация объектов в C++

#include <fstream>
#include <string>
#include <vector>
#include <memory>

class Serializable {
public:
    virtual ~Serializable() = default;
    virtual void serialize(std::ostream& stream) const = 0;
    virtual void deserialize(std::istream& stream) = 0;
};

class NetworkMessage : public Serializable {
private:
    std::string sender_;
    std::string receiver_;
    std::string content_;
    uint64_t timestamp_;
    
public:
    NetworkMessage() : timestamp_(0) {}
    
    NetworkMessage(const std::string& sender, const std::string& receiver, 
                  const std::string& content)
        : sender_(sender), receiver_(receiver), content_(content) {
        timestamp_ = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
    }
    
    void serialize(std::ostream& stream) const override {
        // Запись размеров строк
        size_t senderSize = sender_.size();
        size_t receiverSize = receiver_.size();
        size_t contentSize = content_.size();
        
        stream.write(reinterpret_cast<const char*>(&senderSize), sizeof(senderSize));
        stream.write(sender_.data(), senderSize);
        
        stream.write(reinterpret_cast<const char*>(&receiverSize), sizeof(receiverSize));
        stream.write(receiver_.data(), receiverSize);
        
        stream.write(reinterpret_cast<const char*>(&contentSize), sizeof(contentSize));
        stream.write(content_.data(), contentSize);
        
        stream.write(reinterpret_cast<const char*>(&timestamp_), sizeof(timestamp_));
    }
    
    void deserialize(std::istream& stream) override {
        // Чтение отправителя
        size_t senderSize;
        stream.read(reinterpret_cast<char*>(&senderSize), sizeof(senderSize));
        sender_.resize(senderSize);
        stream.read(&sender_[0], senderSize);
        
        // Чтение получателя
        size_t receiverSize;
        stream.read(reinterpret_cast<char*>(&receiverSize), sizeof(receiverSize));
        receiver_.resize(receiverSize);
        stream.read(&receiver_[0], receiverSize);
        
        // Чтение содержимого
        size_t contentSize;
        stream.read(reinterpret_cast<char*>(&contentSize), sizeof(contentSize));
        content_.resize(contentSize);
        stream.read(&content_[0], contentSize);
        
        // Чтение временной метки
        stream.read(reinterpret_cast<char*>(&timestamp_), sizeof(timestamp_));
    }
    
    // Геттеры
    const std::string& getSender() const { return sender_; }
    const std::string& getReceiver() const { return receiver_; }
    const std::string& getContent() const { return content_; }
    uint64_t getTimestamp() const { return timestamp_; }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Сериализация в Qt с использованием QDataStream

#include <QObject>
#include <QByteArray>
#include <QDataStream>
#include <QFile>
#include <QDebug>

class SerializableObject : public QObject {
    Q_OBJECT
    
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(int id READ id WRITE setId)
    Q_PROPERTY(QByteArray data READ data WRITE setData)
    
private:
    QString name_;
    int id_;
    QByteArray data_;
    
public:
    explicit SerializableObject(QObject* parent = nullptr) 
        : QObject(parent), id_(0) {}
    
    // Геттеры и сеттеры
    QString name() const { return name_; }
    void setName(const QString& name) { name_ = name; }
    
    int id() const { return id_; }
    void setId(int id) { id_ = id; }
    
    QByteArray data() const { return data_; }
    void setData(const QByteArray& data) { data_ = data; }
    
    // Сериализация в QByteArray
    QByteArray toByteArray() const {
        QByteArray byteArray;
        QDataStream stream(&byteArray, QIODevice::WriteOnly);
        stream.setVersion(QDataStream::Qt_6_0);
        
        stream << name_ << id_ << data_;
        return byteArray;
    }
    
    // Десериализация из QByteArray
    bool fromByteArray(const QByteArray& byteArray) {
        if (byteArray.isEmpty()) {
            return false;
        }
        
        QDataStream stream(byteArray);
        stream.setVersion(QDataStream::Qt_6_0);
        
        stream >> name_ >> id_ >> data_;
        return stream.status() == QDataStream::Ok;
    }
    
    // Сериализация в файл
    bool saveToFile(const QString& filename) const {
        QFile file(filename);
        
        if (!file.open(QIODevice::WriteOnly)) {
            qDebug() << "Не удалось открыть файл для записи:" << file.errorString();
            return false;
        }
        
        QDataStream stream(&file);
        stream.setVersion(QDataStream::Qt_6_0);
        
        // Запись заголовка
        stream << QString("SerializableObject");
        stream << quint32(1); // Версия формата
        
        // Запись данных
        stream << name_ << id_ << data_;
        
        file.close();
        return true;
    }
    
    // Десериализация из файла
    bool loadFromFile(const QString& filename) {
        QFile file(filename);
        
        if (!file.open(QIODevice::ReadOnly)) {
            qDebug() << "Не удалось открыть файл для чтения:" << file.errorString();
            return false;
        }
        
        QDataStream stream(&file);
        stream.setVersion(QDataStream::Qt_6_0);
        
        // Чтение заголовка
        QString className;
        quint32 version;
        
        stream >> className >> version;
        
        if (className != "SerializableObject" || version != 1) {
            qDebug() << "Неверный формат файла";
            file.close();
            return false;
        }
        
        // Чтение данных
        stream >> name_ >> id_ >> data_;
        
        file.close();
        return stream.status() == QDataStream::Ok;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Преимущества потоковой обработки

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Эффективность и производительность

#include <iostream>
#include <fstream>
#include <sstream>
#include <chrono>
#include <vector>
#include <algorithm>

class StreamAdvantagesDemo {
public:
    static void demonstrateEfficiency() {
        const size_t ITERATIONS = 1000000;
        
        // Сравнение производительности строковых потоков
        auto stringStreamTime = measureStringStreamPerformance(ITERATIONS);
        auto stringConcatTime = measureStringConcatenationPerformance(ITERATIONS);
        
        std::cout << "StringStream время: " << stringStreamTime << " мс" << std::endl;
        std::cout << "Конкатенация строк время: " << stringConcatTime << " мс" << std::endl;
        std::cout << "Ускорение: " << static_cast<double>(stringConcatTime) / stringStreamTime << "x" << std::endl;
        
        // Демонстрация эффективности обработки больших данных
        demonstrateLargeDataProcessing();
    }
    
private:
    static long long measureStringStreamPerformance(size_t iterations) {
        auto start = std::chrono::high_resolution_clock::now();
        
        for (size_t i = 0; i < iterations; ++i) {
            std::ostringstream oss;
            oss << "Iteration " << i << ": value=" << (i * 2) << ", result=" << (i % 100);
            std::string result = oss.str();
        }
        
        auto end = std::chrono::high_resolution_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    }
    
    static long long measureStringConcatenationPerformance(size_t iterations) {
        auto start = std::chrono::high_resolution_clock::now();
        
        for (size_t i = 0; i < iterations; ++i) {
            std::string result = "Iteration " + std::to_string(i) + 
                               ": value=" + std::to_string(i * 2) + 
                               ", result=" + std::to_string(i % 100);
        }
        
        auto end = std::chrono::high_resolution_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    }
    
    static void demonstrateLargeDataProcessing() {
        // Создание большого файла с данными
        const size_t FILE_SIZE = 100 * 1024 * 1024; // 100 МБ
        createLargeFile("large_data.txt", FILE_SIZE);
        
        // Обработка файла потоками
        auto streamTime = processWithStreams("large_data.txt");
        
        std::cout << "Обработка 100 МБ файла потоками: " << streamTime << " мс" << std::endl;
        
        // Очистка
        std::remove("large_data.txt");
    }
    
    static void createLargeFile(const std::string& filename, size_t size) {
        std::ofstream file(filename);
        const std::string line = "This is a test line for large file processing.\n";
        
        for (size_t i = 0; i < size / line.length(); ++i) {
            file << line;
        }
    }
    
    static long long processWithStreams(const std::string& filename) {
        auto start = std::chrono::high_resolution_clock::now();
        
        std::ifstream file(filename);
        std::string line;
        size_t lineCount = 0;
        size_t charCount = 0;
        
        while (std::getline(file, line)) {
            lineCount++;
            charCount += line.length();
        }
        
        auto end = std::chrono::high_resolution_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Гибкость и расширяемость

#include <iostream>
#include <fstream>
#include <sstream>
#include <memory>
#include <vector>

// Базовый класс для потоков обработки
class ProcessingStream {
public:
    virtual ~ProcessingStream() = default;
    virtual void process(const std::string& data) = 0;
    virtual void flush() = 0;
};

// Поток для логирования
class LoggingStream : public ProcessingStream {
private:
    std::ofstream logFile_;
    
public:
    explicit LoggingStream(const std::string& filename) 
        : logFile_(filename, std::ios::app) {}
    
    void process(const std::string& data) override {
        logFile_ << "[LOG] " << data << std::endl;
    }
    
    void flush() override {
        logFile_.flush();
    }
};

// Поток для шифрования (простая замена символов)
class EncryptionStream : public ProcessingStream {
private:
    std::unique_ptr<ProcessingStream> nextStream_;
    int shift_;
    
public:
    EncryptionStream(std::unique_ptr<ProcessingStream> next, int shift = 3)
        : nextStream_(std::move(next)), shift_(shift) {}
    
    void process(const std::string& data) override {
        std::string encrypted;
        for (char c : data) {
            encrypted += static_cast<char>(c + shift_);
        }
        nextStream_->process(encrypted);
    }
    
    void flush() override {
        nextStream_->flush();
    }
};

// Поток для сжатия (упрощенный RLE)
class CompressionStream : public ProcessingStream {
private:
    std::unique_ptr<ProcessingStream> nextStream_;
    
public:
    CompressionStream(std::unique_ptr<ProcessingStream> next)
        : nextStream_(std::move(next)) {}
    
    void process(const std::string& data) override {
        std::string compressed;
        
        for (size_t i = 0; i < data.length(); ) {
            char current = data[i];
            size_t count = 1;
            
            while (i + count < data.length() && data[i + count] == current && count < 255) {
                count++;
            }
            
            compressed += static_cast<char>(count);
            compressed += current;
            i += count;
        }
        
        nextStream_->process(compressed);
    }
    
    void flush() override {
        nextStream_->flush();
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Цепочка обработки потоков

class StreamChainDemo {
public:
    static void demonstrateChainProcessing() {
        // Создание цепочки: Сжатие -> Шифрование -> Логирование
        auto logger = std::make_unique<LoggingStream>("processed.log");
        auto encryptor = std::make_unique<EncryptionStream>(std::move(logger), 5);
        auto compressor = std::make_unique<CompressionStream>(std::move(encryptor));
        
        // Обработка данных через цепочку
        std::vector<std::string> testData = {
            "Hello World! This is a test message.",
            "AAAAABBBBCCCDDEEF", // Данные для сжатия
            "Another test message with different content."
        };
        
        for (const auto& data : testData) {
            compressor->process(data);
        }
        
        compressor->flush();
        
        std::cout << "Обработка данных через цепочку потоков завершена." << std::endl;
        std::cout << "Результаты сохранены в processed.log" << std::endl;
    }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Заключение

Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Ключевые преимущества потоков ввода/вывода

  1. Унификация интерфейса - единый способ работы с различными источниками данных
  2. Безопасность типов - компилятор контролирует корректность операций
  3. Автоматическая обработка ошибок - исключения и коды состояния
  4. Гибкость и расширяемость - легкость в добавлении новых типов потоков
  5. Производительность - оптимизированная буферизация и обработка
  6. Совместимость - стандартизированный интерфейс across различных платформ
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Лучшие практики

  1. Используйте RAII - автоматическое управление ресурсами
  2. Проверяйте состояние потоков - всегда проверяйте результат операций
  3. Обрабатывайте исключения - корректная обработка ошибок
  4. Используйте буферизацию - для улучшения производительности
  5. Закрывайте потоки - явное закрытие при завершении работы
  6. Используйте современные возможности C++ - умные указатели и автоматическое управление
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Интеграция с Qt

  • QFile и QTextStream для текстовых файлов
  • QDataStream для бинарных данных и сериализации
  • QBuffer для работы с данными в памяти
  • QTcpSocket и QUdpSocket для сетевых потоков
  • Сигналы и слоты для асинхронной обработки
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений

Вопросы для самопроверки

  1. Какие основные классы используются для работы с файлами в C++?
  2. Чем отличается текстовый режим от бинарного при работе с файлами?
  3. Как реализовать безопасное чтение данных из файла?
  4. Какие преимущества дает использование потоков в Qt?
  5. Как организовать эффективную работу с большими файлами?
  6. Что такое сериализация и где она применяется?
  7. Как обеспечить потокобезопасность при работе с файлами?
  8. Какие существуют методы оптимизации производительности I/O операций?
  9. Как реализовать пользовательские манипуляторы для потоков?
  10. Как обрабатывать ошибки при работе с потоками ввода/вывода?
Потоки ввода/вывода и работа с файлами

Заметки докладчика: - Потоки ввода-вывода — фундамент для сетевого программирования: чтение конфигурационных файлов, сериализация данных для передачи по сети, логирование активности серверов и клиентов. - Это самая объёмная лекция курса. Планируйте время: не менее 2 пар. Рекомендуется разбить на две части: C++ потоки и Qt потоки. - Упомяните, что концепция потоков (streams) используется практически во всех сетевых протоколах — TCP-соединение абстрактно представляет собой двунаправленный поток байтов.

Заметки докладчика: - Ключевые классы, которые нужно запомнить: ifstream/ofstream для файлов, istringstream/ostringstream для строковых манипуляций (парсинг, форматирование). - В Qt аналоги: QTextStream — для текста, QDataStream — для бинарных данных с автоматическим управлением порядком байтов. - Важный момент: все классы наследуются от ios_base, поэтому флаги форматирования (hex, fixed и др.) едины для всех потоков. - Полиморфизм потоков: функция, принимающая std::istream&, может работать с файлом, строкой или консолью — это используется при написании тестов (подменяем файл на строковый поток).

Заметки докладчика: - std::getline(cin, str) читает всю строку до '\n', в отличие от operator>>, который останавливается на пробеле. Это критически важно при чтении строк после числового ввода — в буфере остаётся '\n'. - В сетевом программировании getline используется для чтения строк протоколов: HTTP-заголовки (строка за строкой), SMTP-команды, IRC-сообщения. - Покажите типичную ошибку: cin >> age; getline(cin, name); — getline прочитает пустую строку из-за остатка '\n' в буфере. Решение: cin.ignore() перед getline.

Заметки докладчика: - Форматирование важно для логирования сетевой активности: std::fixed + std::setprecision — для вывода временных меток (rtt, timeout), std::hex — для отображения дампов бинарных протоколов (пакетных данных). - std::setw + std::setfill полезны для табличного вывода таблиц маршрутизации, таблиц ARP, логов соединений. - Манипуляторы «липкие» (sticky): std::hex, std::fixed, std::left действуют до следующего изменения. std::setw — «нелипкий», применяется только к следующему выводу. - В Qt аналоги: QTextStream::setFieldWidth(), setPadChar(), setIntegerBase(), setRealNumberPrecision().

Заметки докладчика: - Бинарная сериализация используется при передаче структурированных данных по сети. Обратите внимание на sizeof и выравнивание структур — это частый источник ошибок при межплатформенной передаче. - Сетевой порядок байтов (network byte order) — big-endian. Если платформа little-endian (x86/x64), необходимо преобразование: htonl/htons/ntohl/ntohs. В C++20: std::endian. - Класс DataRecord с char name[32] — пример фиксированного размера. В реальных сетевых протоколах строки часто имеют префикс длины (length-prefixed), как в примере NetworkMessage дальше. - QDataStream автоматически обрабатывает порядок байтов (по умолчанию big-endian) и длину строк — это значительно упрощает сетевое программирование в Qt.

Заметки докладчика: - QFile — основной класс Qt для файловых операций. Работает как с локальными, так и с сетевыми ресурсами (через QNetworkAccessManager). - QTextStream автоматически определяет кодировку (UTF-8 по умолчанию в Qt 6), обрабатывает BOM, преобразует окончания строк (\r\n, \r, \n). - QDataStream — для бинарной сериализации: записывает/читает типы Qt (QString, QByteArray, QVector и др.) с метаданными (длина строки, тип). Важно: всегда устанавливать версию setVersion(QDataStream::Qt_6_0) для совместимости. - Практический совет: для сетевой передачи используйте QDataStream поверх QTcpSocket — получите готовую сериализацию с контролем порядка байтов.

Заметки докладчика: - Работа с каталогами — обязательный пункт учебной программы (тема 6). - std::filesystem (C++17) — современный кроссплатформенный API для работы с файловой системой. Предпочтителен для нового кода. - QDir — Qt-аналог, имеет больше удобных методов (entryList с фильтрами, nameFilters). - Для сетевых приложений: типичная задача — чтение конфигурации из директории, управление лог-файлами, работа с сертификатами. - QDir::separator() возвращает правильный разделитель для платформы (/ или \).

Заметки докладчика: - Сериализация — преобразование объекта в последовательность байтов для хранения или передачи по сети. Перед отправкой по сокету данные всегда сериализуются. - Два основных подхода: бинарная сериализация (компактно, быстро, но нечитаемо и хрупко при изменении формата) и текстовая/JSON (читаемо, самодокументируемо, но больше размер и медленнее). - В сетевом программировании: Protocol Buffers (Google) — эффективная бинарная сериализация с описанием схемы (.proto); JSON — для REST API; MessagePack — бинарный JSON-подобный формат. - Ключевое правило: всегда указывайте версию формата (как в примере SerializableObject с quint32 version) — это позволяет эволюционировать протокол без поломки совместимости.

Заметки докладчика: 1. ifstream, ofstream, fstream — для текстовых и бинарных файлов; istringstream, ostringstream, stringstream — для работы со строками. 2. Текстовый режим преобразует символы новой строки (\n → \r\n на Windows), бинарный (std::ios::binary) записывает данные без преобразования. 3. Всегда проверять is_open() перед чтением, проверять stream.good() после операций, использовать seekg/tellg для контроля позиции, обрабатывать EOF. 4. Единый интерфейс через QFile/QTextStream/QDataStream, автоматическая работа с кодировками, интеграция с сигнально-слотовым механизмом Qt, поддержка версионирования форматов. 5. Построчное чтение (std::getline, readLine()), буферизация (pubsetbuf), использование mmap для очень больших файлов, поточная обработка без загрузки всего файла в память. 6. Сериализация — преобразование объекта в последовательность байтов для хранения или передачи. Применяется: сохранение конфигурации, передача данных по сети, persistence объектов, межпроцессное взаимодействие. 7. Использовать мьютексы (std::mutex) или QLockFile, избегать одновременной записи из нескольких потоков, применять атомарные операции для флагов. 8. Буферизация (большой буфер), пакетная запись вместо посимвольной, асинхронное I/O, memory-mapped файлы, минимизация числа системных вызовов. 9. Определить функцию или объект с перегруженным operator<< для std::ostream, использовать std::setw, std::setfill, std::hex и другие манипуляторы. 10. Проверять флаги состояния (failbit, badbit), использовать exceptions() для автоматического выброса исключений, всегда закрывать файлы через RAII, логировать ошибки.